package query

import (
	"fmt"
	"net/http"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/glennliao/apijson-go/config"
	"github.com/glennliao/apijson-go/consts"
	"github.com/glennliao/apijson-go/model"
	"github.com/glennliao/apijson-go/util"
	"github.com/gogf/gf/v2/util/gconv"
)

type C查询节点 struct {
	J节点 *J节点
}

func X新建查询节点(n *J节点) *C查询节点 {
	return &C查询节点{J节点: n}
}

func (q *C查询节点) J解析() {
	当前节点 := q.J节点

	访问配置, 错误 := 当前节点.C查询上下文.C查询配置.H获取访问配置(当前节点.J键, 当前节点.C查询上下文.B不验证访问)
	if 错误 != nil {
		当前节点.C错误 = 错误
		return
	}

	当前节点.Z执行器配置 = config.X新建执行器配置(访问配置, http.MethodGet, 当前节点.C查询上下文.B不验证访问)
	当前节点.Z执行器配置.S数据库字段风格 = 当前节点.C查询上下文.S数据库字段风格
	当前节点.Z执行器配置.JSON字段风格 = 当前节点.C查询上下文.Json字段风格
	当前节点.Z执行器配置.S数据库元信息 = 当前节点.C查询上下文.S数据库元数据

	if 当前节点.S是列表 {
		fieldsGet := 当前节点.Z执行器配置.G根据角色获取字段获取配置()
		if *fieldsGet.MaxCount != 0 {
			if 当前节点.F分页参数.T条数 > *fieldsGet.MaxCount {

				当前节点.C错误 = consts.X新建请求验证错误(" > maxCount: " + 当前节点.L路径)
				return
			}
		}
	}

	var 访问条件 model.MapStrAny

	S设置节点角色(当前节点, 当前节点.J键, 当前节点.J角色)
	当前节点.Z执行器配置.S设置角色(当前节点.J角色)

	if 当前节点.J角色 == consts.J拒绝访问 {
		当前节点.C错误 = consts.X新建拒绝访问错误(当前节点.J键, 当前节点.J角色)
		return
	}

	// if 当前节点.J角色 == consts.ROLE {
	// 	当前节点.C错误 = consts.NewRoleErr
	// 	return
	// }

	if 当前节点.C查询上下文.B不验证访问 == false {
		有权限, 条件, 错误 := J检查访问权限(当前节点)
		if 错误 != nil {
			当前节点.C错误 = 错误
			return
		}

		if !有权限 {
			当前节点.C错误 = consts.X新建无访问权限错误(当前节点.J键, 当前节点.J角色)
			return
		}

		访问条件 = 条件.H获取所有条件()
	}

	查询执行器, 错误 := X新建执行器(当前节点.Z执行器配置.H获取执行器名称(), 当前节点.S上下文, 当前节点.Z执行器配置)
	if 错误 != nil {
		当前节点.C错误 = 错误
		return
	}

	当前节点.Z执行器 = 查询执行器

	// 解析查询条件
	引用键映射, 条件映射, 控制映射 := J解析查询节点请求(当前节点.Q请求数据, 当前节点.S是列表)

	当前节点.Z执行器.J解析控制(控制映射)

	if 值, 存在 := 控制映射[consts.L列]; 存在 {
		var 表达式 = regexp.MustCompile(`^[\s\w][\w()]+`) // 匹配 field, COUNT(field)

		字段字符串 := strings.ReplaceAll(gconv.String(值), ";", ",")

		字段列表 := strings.Split(字段字符串, ",")

		for i, 项 := range 字段列表 {
			字段列表[i] = 表达式.ReplaceAllStringFunc(项, func(字段 string) string {
				return 字段
			})
		}

		for _, 项 := range 字段列表 {
			if strings.Contains(项, ":") {
				当前节点.Z字段映射[项] = 项
			} else {
				当前节点.Z字段映射[项] = 项
			}
		}

		fmt.Println(字段列表)
	}

	错误 = 当前节点.Z执行器.J解析条件(条件映射, true)
	if 错误 != nil {
		当前节点.C错误 = 错误
		return
	}

	错误 = 当前节点.Z执行器.J解析条件(访问条件, false)
	if 错误 != nil {
		当前节点.C错误 = 错误
		return
	}

	当前节点.Z主表键 = filepath.Base(当前节点.L路径)

	if len(引用键映射) > 0 { // 需要引用别处
		当前节点.Y引用键映射 = make(map[string]J节点引用)
		有兄弟引用 := false // 是否引用兄弟节点, 列表中的主表不能依赖兄弟节点

		for 引用键, 引用字符串 := range 引用键映射 {
			if strings.HasPrefix(引用字符串, "/") { // 这里/开头是相对同级
				引用字符串 = filepath.Dir(当前节点.L路径) + 引用字符串
			}

			引用路径, 引用列 := util.X解析引用列(引用字符串)

			if !有兄弟引用 {
				if filepath.Dir(当前节点.L路径) == filepath.Dir(引用路径) {
					有兄弟引用 = true
				}
			}

			if 引用路径 == 当前节点.L路径 { // 不能依赖自身
				当前节点.C错误 = consts.X新建请求验证错误(fmt.Sprintf("node cannot ref self: (%s) {%s:%s}", 引用路径, 引用键, 引用字符串))
				return
			}

			引用节点 := 当前节点.C查询上下文.L路径节点映射[引用路径]
			if 引用节点 == nil {
				当前节点.C错误 = consts.X新建请求验证错误(fmt.Sprintf(" node %s is nil, but ref by %s", 引用路径, 当前节点.L路径))
				return
			}

			if 引用节点.C错误 != nil {
				当前节点.C错误 = 引用节点.C错误
				return
			}

			for _, 已引用节点 := range 引用节点.Y引用键映射 {
				if 已引用节点.J节点.L路径 == 当前节点.L路径 {
					当前节点.C错误 = consts.X新建请求验证错误(fmt.Sprintf("circle ref %s & %s", 引用节点.L路径, 当前节点.L路径))
					return
				}
			}

			当前节点.Y引用键映射[引用键] = J节点引用{
				Z字段: 引用列,
				J节点: 引用节点,
			}
		}

		if 有兄弟引用 {
			当前节点.Z主表键 = ""
		}
	}
}
func (q *C查询节点) H获取数据() {
	当前节点 := q.J节点
	for 引用键, 引用节点 := range 当前节点.Y引用键映射 {
		结果, 错误 := 引用节点.J节点.J结果()
		if 错误 != nil {
			当前节点.C错误 = 错误
			return
		}

		if 引用节点.J节点.S是列表 {
			列表 := 结果.([]model.Map)

			值列表 := H获取列值列表(列表, 引用节点.Z字段)
			if len(值列表) == 0 { // 未查询到主表,故当前不再查询
				当前节点.Z执行器.S设置空结果()
				break
			}

			错误 = 当前节点.Z执行器.J解析条件(model.MapStrAny{
				引用键 + consts.C操作符包含: 值列表, // @与{}&等的结合 id{}@的处理
			}, false)

			if 错误 != nil {
				当前节点.C错误 = 错误
				return
			}

		} else {

			if 结果 == nil { // 未查询到主表,故当前不再查询
				当前节点.Z执行器.S设置空结果()
				break
			}

			项 := 结果.(model.Map)

			引用值 := 项[引用节点.Z字段]

			var 引用条件映射 = model.MapStrAny{
				引用键: 引用值,
			}
			错误 = 当前节点.Z执行器.J解析条件(引用条件映射, false)
			if 错误 != nil {
				当前节点.C错误 = 错误
				return
			}
		}
	}

	if 当前节点.S是列表 {

		页码 := 当前节点.F分页参数.Y页码
		数量 := 当前节点.F分页参数.T条数

		if 当前节点.Z主表键 == "" { // 主查询表才分页
			页码 = 0
			数量 = 0
		}

		当前节点.Z总数, 当前节点.C错误 = 当前节点.Z执行器.C查询总数()
		if 当前节点.Z总数 > 0 {
			当前节点.F返回值, 当前节点.C错误 = 当前节点.Z执行器.C查询列表(页码, 数量)
		}

	} else {
		当前节点.F返回值, 当前节点.C错误 = 当前节点.Z执行器.C查询单条()
	}
	if 当前节点.C错误 != nil {
		return
	}

	if 当前节点.F返回值 != nil { // parse时判断是否有functions,有则处理
		查询配置 := 当前节点.C查询上下文.C查询配置

		// 需优化调整
		// 处理functions调用
		for 键, 值 := range 当前节点.Q请求数据 {
			if !strings.HasSuffix(键, consts.C操作符减) {
				continue
			}

			键 = 键[0 : len(键)-len(consts.H函数键后缀)]

			函数名, 参数键列表 := util.J解析函数字符串(值.(string))
			函数 := 查询配置.H获取函数(函数名)

			if 函数 == nil {
				当前节点.C错误 = consts.X新建请求验证错误("func not exists：" + 函数名)
				return
			}

			if 当前节点.S是列表 {

				// todo 统一func的调用？

				// 组装参数
				var 参数列表 []model.Map
				结果列表 := 当前节点.F返回值.([]model.Map)
				for _, 结果 := range 结果列表 {
					参数 := model.Map{}
					for 参数索引, 参数项 := range 函数.C参数列表 {
						参数键 := 参数键列表[参数索引]
						if 参数项.M名称 == consts.H函数原始请求参数 {
							参数[参数项.M名称] = util.Z转字符串(结果)
						} else {
							if strings.HasPrefix(参数键, "'") && strings.HasSuffix(参数键, "'") {
								参数[参数项.M名称] = 参数键[1 : len(参数键)-1]
							} else {
								参数[参数项.M名称] = util.Z转字符串(结果[参数键])
							}
						}
					}
					参数列表 = append(参数列表, 参数)
				}

				if 函数.P批量处理 {

					参数 := model.Map{}

					值列表, 错误 := 查询配置.T调用函数(当前节点.S上下文, 函数名, 参数)
					if 错误 != nil {
						当前节点.C错误 = 错误
						return
					}
					列表 := gconv.Interfaces(值列表)
					for i := range 结果列表 {
						结果列表[i][键] = 列表[i]
					}

				} else {
					for i, 参数 := range 参数列表 {
						值, 错误 := 查询配置.T调用函数(当前节点.S上下文, 函数名, 参数)
						if 错误 != nil {
							当前节点.C错误 = 错误
							return
						}
						结果列表[i][键] = 值
					}
				}

			} else {
				参数 := model.Map{}
				for 参数索引, 参数项 := range 函数.C参数列表 {
					参数键 := 参数键列表[参数索引]
					if 参数项.M名称 == consts.H函数原始请求参数 {
						参数[参数项.M名称] = util.Z转字符串(当前节点.F返回值)
					} else {
						if strings.HasPrefix(参数键, "'") && strings.HasSuffix(参数键, "'") {
							参数[参数项.M名称] = 参数键[1 : len(参数键)-1]
						} else {
							参数[参数项.M名称] = util.Z转字符串(当前节点.F返回值.(model.Map)[参数键])
						}
					}
				}

				值, 错误 := 查询配置.T调用函数(当前节点.S上下文, 函数名, 参数)
				if 错误 != nil {
					当前节点.C错误 = 错误
					return
				}
				当前节点.F返回值.(model.Map)[键] = 值
			}
		}
	}
}
func (q *C查询节点) C处理结果() {
	当前节点 := q.J节点
	if 当前节点.S是列表 {
		if 当前节点.F返回值 == nil || 当前节点.F返回值.([]model.Map) == nil {
			当前节点.F返回值 = []model.Map{}
		}
	} else {
		if 当前节点.F返回值 == nil || 当前节点.F返回值.(model.Map) == nil {
			当前节点.F返回值 = nil
		}
	}
}

func (q *C查询节点) J节点类型() int {
	return J节点类型查询
}
